
#include "pong.h"
#include "st1633.h"
#include "st7567s.h"
#include "YDDO0196AAFD.h"
#include <stdlib.h>

typedef uint8_t byte;
typedef uint16_t u16;
enum boolean { false = 0, true = 1 };

#define LCD_X 110
#define LCD_Y 5
#define LCD_Y_BLANK 4
#define ballPerimeter 4
#define barWidth 3
#define barHeight 10

unsigned int bar1X = 0;
unsigned int bar1Y = 0;
//unsigned int bar2X = 0;
unsigned int bar2X = LCD_X - barWidth;
//unsigned int bar2Y = LCD_Y * 8 - barHeight;
unsigned int bar2Y = 0;

int ballX = 0;
int ballY = 0;

enum boolean isBallUp = false;
enum boolean isBallRight = true;

byte gameState = 1;
byte ballSpeed = 1;
byte player1WinCount = 0;
byte player2WinCount = 0;
byte hitCount = 0;

extern uint8_t tpInt;
uint8_t buf[20];
uint16_t P1Y, P2Y;

unsigned long millis() { return HAL_GetTick(); }
void LcdInitialise(void);
void touchInitial(void);
void restartGame(void);

void update(void);
void refreshScreen(void);
void updateTouchPosition(void);


void setup(void);
void loop(void);



void pong(void)
{
	printf("Pong START\r\n");

	setup();

	loop();
}

void setup(void)
{
	LcdInitialise();
	touchInitial();
	restartGame();
}

void loop(void)
{
	unsigned long lcdRefreshTime = 0;
    const int lcdRefreshInterval = 150;
    unsigned long tpLastFetchTime = 0;
    const int tpFetchInterval = 20;
    uint32_t now;

    while(1)
    {
        now = millis();
        
        if (now - lcdRefreshTime > lcdRefreshInterval) {
            update();
            refreshScreen();
            lcdRefreshTime = now;
        }

        if(now - tpLastFetchTime > tpFetchInterval) {
            updateTouchPosition();
            tpLastFetchTime = now;
        }
    }
}

void drawScore(void){
    o0196_draw_digit(5, player1WinCount / 100);
    o0196_draw_digit(6, player1WinCount / 10);
    o0196_draw_digit(7, player1WinCount % 10);

    o0196_draw_digit(9, player2WinCount / 100);
    o0196_draw_digit(10, player2WinCount / 10);
    o0196_draw_digit(11, player2WinCount % 10);
}

void restartGame(void)
{
	ballSpeed = 3;
	gameState = 1;
	//ballX = rand() % 60;
    ballX = LCD_X / 2;
	//ballY = 20;
    ballY = rand() % 32;
	isBallUp = false;
	isBallRight = true;
	hitCount = 0;
    drawScore();
}

void refreshScreen(void)
{
	if (gameState == 1) {
		for (int y = 0; y < LCD_Y; y++) {
			for (int x = 0; x < LCD_X; x++) {
				byte pixel = 0x00;
				int realY = y * 8;
				// draw ball if in the frame
				if (x >= ballX &&
				    x <= ballX + ballPerimeter - 1 &&
				    ballY + ballPerimeter > realY &&
				    ballY < realY + 8) {
					byte ballMask = 0x00;
					for (int i = 0; i < realY + 8 - ballY;
					     i++) {
						ballMask = ballMask >> 1;
						if (i < ballPerimeter)
							ballMask =
							    0x80 | ballMask;
					}
					pixel = pixel | ballMask;
				}

				// draw bars if in the frame
				if (x >= bar1X && x <= bar1X + barWidth - 1 &&
				    bar1Y + barHeight > realY &&
				    bar1Y < realY + 8) {
					byte barMask = 0x00;
					for (int i = 0; i < realY + 8 - bar1Y;
					     i++) {
						barMask = barMask >> 1;
						if (i < barHeight)
							barMask =
							    0x80 | barMask;
					}
					pixel = pixel | barMask;
				}

				if (x >= bar2X && x <= bar2X + barWidth - 1 &&
				    bar2Y + barHeight > realY &&
				    bar2Y < realY + 8) {
					byte barMask = 0x00;
					for (int i = 0; i < realY + 8 - bar2Y;
					     i++) {
						barMask = barMask >> 1;
						if (i < barHeight)
							barMask =
							    0x80 | barMask;
					}
					pixel = pixel | barMask;
				}
				st7567_drawByte(y, x, pixel);
			}
		}
	} else if (gameState == 2) {
        drawScore();
	}
}

void update(void)
{
	if (gameState == 1) {
        int barMargin = LCD_Y * 8 - LCD_Y_BLANK - barHeight;

        bar1Y = (P1Y-360)/6;
		bar2Y = (P2Y-360)/6;

		if (bar1Y > barMargin)
			bar1Y = barMargin;
		if (bar2Y > barMargin)
			bar2Y = barMargin;

		// move the ball now
		if (isBallUp)
			ballY -= ballSpeed;
		else
			ballY += ballSpeed;
		if (isBallRight)
			ballX += ballSpeed;
		else
			ballX -= ballSpeed;

		// check collisions
        if (ballY < 1) {
			isBallUp = false;
			ballY = 0;
		} else if (ballY > LCD_Y*8 - LCD_Y_BLANK - ballPerimeter - 1) {
			isBallUp = true;
			ballY = LCD_Y * 8 - LCD_Y_BLANK - ballPerimeter;
		}

		if (ballX < barWidth) {
			if (ballY + ballPerimeter >= bar1Y &&
			    ballY <=
				bar1Y + barHeight) { // ball bounces from bar1
				isBallRight = true;
				if (ballY + ballPerimeter / 2 <
				    bar1Y + barHeight / 2)
					isBallUp = true;
				else
					isBallUp = false;
				ballX = barWidth;
				if (++hitCount % 10 == 0 && ballSpeed < 5)
					ballSpeed++;
			} else { // player2 wins
				gameState = 2;
				player2WinCount++;
			}
		}

		if (ballX + ballPerimeter > LCD_X - barWidth) {
			if (ballY + ballPerimeter >= bar2Y &&
			    ballY <=
				bar2Y + barHeight) { // ball bounces from bar2
				isBallRight = false;
				if (ballY + ballPerimeter / 2 <
				    bar2Y + barHeight / 2)
					isBallUp = true;
				else
					isBallUp = false;
				ballX = LCD_X - barWidth - ballPerimeter;
				if (++hitCount % 10 == 0 && ballSpeed < 5)
					ballSpeed++;
			} else { // player 1 wins
				gameState = 2;
				player1WinCount++;
			}
		}
	} else if (gameState == 2) {
		for (int i = 0; i < 4; i++) {
			// LCD in inverse mode.
			st7567_command(0xA7);
			HAL_Delay(300);
			st7567_command(0xA6);
			HAL_Delay(300);
		}
		restartGame();
	}
}

void LcdInitialise(void)
{
	HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET);
	HAL_Delay(10);

	HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET);
	HAL_Delay(10);

	st7567s_initial();
    st7567_fill(0x00);

    for(uint16_t i = 1; i <= ICON_GROUP_A_MAX; i++){
        o0196_drawIcon(ICON_GROUP_A, i, 1);
        HAL_Delay(100);
        o0196_drawIcon(ICON_GROUP_A, i, 0);
    }

    for(uint16_t i = 1; i <= ICON_GROUP_B_MAX; i++){
        o0196_drawIcon(ICON_GROUP_B, i, 1);
        HAL_Delay(100);
        o0196_drawIcon(ICON_GROUP_B, i, 0);
    }

    st7567_command(0xA7);
    HAL_Delay(200);
    st7567_command(0xA6);
    

    printf("LCD ST5767S Initial Success\r\n");
    // TODO
	// st7567_fillLine(0x00);
}

void touchInitial(void)
{
	int result = 0;
	result = ST1633_Init();
    if(result != 0){
        printf("Touch ST1633 Initial Failed\r\n");
    } else {
        printf("Touch ST1633 Initial Success\r\n");
    }
}

void updateTouchPosition(void){
    u16 x, y;
    if (tpInt == 1) {
        ST1633_RD_Reg(0x12, buf, 20);

        for (uint8_t i = 0; i < 5; i++) {
            if (( buf[i * 4] & 0x80) == 0) {
                // printf("point %d not valid\r\n", i);
                continue;
            } else {
                //printf("point %d valid\r\n", i);
                x = (u16)((buf[i * 4] & 0x70) << 4 |
                    buf[i * 4 + 1]);
                y = (u16)((buf[i * 4] & 0x07) << 8 |
                    buf[i * 4 + 2]);

                printf(" touch %d: X=%d,Y=%d\r\n", i, x, y);

                // Y 
                if (y > 360 && y < 560){
                    if(x > 20 && x < 120){
                        // left 
                        P1Y = y;
                    }

                    if(x > 360 && x < 460){
                        // right
                        P2Y = y;
                    }
                }
            }
        }
        tpInt = 0;
    }
}
